#include "Gk.h"

#ifdef GK_DEVICE_DIRECTX9

#pragma warning (disable:4100)	// disable unused parameter warning until everything is implemented

#include "GkDevice.DirectX9.h"
#include "..\GkContext.GenericDeferred.h"

#pragma comment( lib, "D3D9.lib" )
#pragma comment( lib, "D3DX9.lib" )

namespace gk
{
	DeviceHandle CreateDevice()
	{
		return new DeviceDirectX9;
	}

	void DeviceDirectX9::Initialise(InitParamsHandle hInitParams)
	{
		DeviceCommon::InitialisePlatform(hInitParams);

		if( NULL == ( m_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
		{
			CR_ERROR_STRING("Error initialising DX9");
		}

		D3DPRESENT_PARAMETERS d3dpp;
		ZeroMemory(&d3dpp, sizeof(d3dpp));
		d3dpp.Windowed = true;
		d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
		d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
		d3dpp.BackBufferCount = 1;

		UINT		adapterToUse	= D3DADAPTER_DEFAULT;
		D3DDEVTYPE	deviceType		= D3DDEVTYPE_HAL;
		DWORD		behaviourFlags	= D3DCREATE_HARDWARE_VERTEXPROCESSING;

#if GK_DEVICE_ALLOW_NVPERFHUD
		// Look for 'NVIDIA PerfHUD' adapter
		// If it is present, override default settings
		for (UINT Adapter=0;Adapter<m_pD3D->GetAdapterCount();Adapter++)
		{
			D3DADAPTER_IDENTIFIER9 Identifier;
			HRESULT Res;
			Res = m_pD3D->GetAdapterIdentifier(Adapter,0,&Identifier);
			if (strstr(Identifier.Description,"PerfHUD") != 0)
			{
				adapterToUse=Adapter;
				deviceType=D3DDEVTYPE_REF;
				behaviourFlags = D3DCREATE_HARDWARE_VERTEXPROCESSING;
				break;
			}
		}
#endif

		if( m_pD3D->CreateDevice( 
			adapterToUse, 
			deviceType, 
			(HWND)pf::GetPlatform()->GetPlatformWindowHandle(),
			behaviourFlags,
			&d3dpp, 
			&m_pD3DDevice ) )
		{
			CR_ERROR_STRING("Error creating D3D device");
		}

		cr::ClearObject(GetBackBuffer());
		GetBackBuffer()->m_width  = GetDisplayConfig()->m_width;
		GetBackBuffer()->m_height = GetDisplayConfig()->m_height;
		GetBackBuffer()->m_format = gk::BufferFormat::R8G8B8;
		GetBackBuffer()->m_multisampleType = MultisampleType::None;
		m_pD3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &GetBackBuffer()->m_internal.m_pSurface);

		DeviceCommon::InitialiseCommon(hInitParams);
	}

	void DeviceDirectX9::Shutdown()
	{
		DeviceCommon::ShutdownCommon();

		ReleaseRenderSurface(GetBackBuffer());

		if( m_pD3DDevice != NULL )
		{
			m_pD3DDevice->Release();
			m_pD3DDevice = 0;
		}

		if( m_pD3D != NULL )
		{
			m_pD3D->Release();
			m_pD3D = 0;
		}

		DeviceCommon::ShutdownPlatform();
	}

	bool DeviceDirectX9::Present()
	{
		m_pD3DDevice->Present( NULL, NULL, NULL, NULL );
		return pf::GetPlatform()->Present();
	}

	// Object creation
	void DeviceDirectX9::CreateContext(ContextHandle hContext)
	{
		switch (hContext->m_type)
		{
		case ContextType::Immediate:
			{
				hContext->m_internal.m_pD3DDevice = m_pD3DDevice;
				break;
			}

		case ContextType::Deferred:
			{
				//ContextGenericDeferred newContext;
				//hContext->m_dataBlock.Assign(newContext);
				break;
			}
		}
	}

	void DeviceDirectX9::CreateBuffer(BufferHandle hBuffer)
	{
		BufferInternal& bufferDirectInternal = hBuffer->m_internal;
		cr::ClearObject(bufferDirectInternal);

		cr::size_t bufferSize = hBuffer->m_stride * hBuffer->m_numElements;

		switch (hBuffer->m_format)
		{
		case BufferFormat::Index16Stream:
		case BufferFormat::Index32Stream:
			{
				m_pD3DDevice->CreateIndexBuffer( 
					(UINT)(bufferSize),
					D3DUSAGE_WRITEONLY, 
					TypeConversion::ConvertBufferFormat(hBuffer->m_format),
					D3DPOOL_DEFAULT, 
					&bufferDirectInternal.m_pIndexBuffer, 
					NULL );
				CR_ASSERT(bufferDirectInternal.m_pIndexBuffer);				 
				break;
			}

		case BufferFormat::VertexStream:
			{
				m_pD3DDevice->CreateVertexBuffer(
					(UINT)(bufferSize),
					D3DUSAGE_WRITEONLY, 
					0,
					D3DPOOL_MANAGED, 
					&bufferDirectInternal.m_pVertexBuffer, 
					NULL );
				CR_ASSERT(bufferDirectInternal.m_pVertexBuffer);
				break;
			}

		case BufferFormat::ConstantBuffer:
			{
				CR_ERROR_STRING("Unsupported");
				break;
			}

		default:
			{
				CR_ERROR_STRING("Unsupported");
				break;
			}
		}

		if (hBuffer->m_pInitData)
		{
			CR_ASSERT(hBuffer->m_initDataSize <= bufferSize);
			hBuffer->Lock();
			hBuffer->Write(hBuffer->m_pInitData, hBuffer->m_initDataSize);
			hBuffer->Unlock();
		}
	}

	void DeviceDirectX9::CreateRenderTarget(RenderTargetHandle hRenderTarget)
	{
		// See if this render target is blank
		bool blank = !hRenderTarget->m_hDepthSurface.IsValid();
		cr::size_t i = 0;
		while (blank && i < Constants::MaxColourTargets)
		{
			if (hRenderTarget->m_hColourSurface[i].IsValid())
			{
				blank = false;
				break;
			}
			++i;
		}

		if (blank)
		{
			// Setup as system render target
			hRenderTarget->m_hColourSurface[0] = GetBackBuffer();
		}

		CR_ASSERT_MSG(!hRenderTarget->m_hStencilSurface.IsValid() || !hRenderTarget->m_hDepthSurface.IsValid() || hRenderTarget->m_hDepthSurface == hRenderTarget->m_hStencilSurface,
			"DirectX9 doesn't support seperate depth and stencil buffers");
	}

	void DeviceDirectX9::CreateRenderSurface(RenderSurfaceHandle hRenderSurface)
	{
		RenderSurfaceInternal&	renderSurfaceInternal = hRenderSurface->m_internal;

		LPDIRECT3DSURFACE9		renderTarget			= 0;
		LPDIRECT3DTEXTURE9		renderTargetAsTexture	= 0;

		bool isDepthSurface = BufferFormat::IsDepthFormat(hRenderSurface->m_format);
		D3DMULTISAMPLE_TYPE msType = hRenderSurface->m_multisampleType == MultisampleType::None ? D3DMULTISAMPLE_NONE : D3DMULTISAMPLE_2_SAMPLES;
		HRESULT hRes = 0xffffffff;

		if (isDepthSurface)
		{
			if (msType == D3DMULTISAMPLE_NONE)
			{
				hRes = m_pD3DDevice->CreateTexture(
					(UINT)hRenderSurface->m_width,
					(UINT)hRenderSurface->m_height,
					1,
					D3DUSAGE_DEPTHSTENCIL,
					TypeConversion::ConvertBufferFormat(hRenderSurface->m_format),
					D3DPOOL_DEFAULT,
					&renderTargetAsTexture,
					NULL
					);
				CR_ASSERT(D3D_OK == hRes);
			}
			else
			{
				hRes = m_pD3DDevice->CreateDepthStencilSurface(
					(UINT)hRenderSurface->m_width,
					(UINT)hRenderSurface->m_height, 
					TypeConversion::ConvertBufferFormat(hRenderSurface->m_format),
					msType,
					0,
					TRUE,
					&renderTarget,
					NULL
					);
			}
		}
		else
		{
			if (hRenderSurface->m_createTexture)
			{
				hRes = m_pD3DDevice->CreateTexture(
					(UINT)hRenderSurface->m_width,
					(UINT)hRenderSurface->m_height,
					1,
					D3DUSAGE_RENDERTARGET,
					TypeConversion::ConvertBufferFormat(hRenderSurface->m_format),
					D3DPOOL_DEFAULT,
					&renderTargetAsTexture,
					NULL
					);
			}
			else
			{
				hRes = m_pD3DDevice->CreateRenderTarget(
					(UINT)hRenderSurface->m_width,
					(UINT)hRenderSurface->m_height, 
					TypeConversion::ConvertBufferFormat(hRenderSurface->m_format),
					msType,
					0,
					msType == D3DMULTISAMPLE_NONE,
					&renderTarget,
					NULL
					);
			}
		}

		CR_ASSERT(hRes == D3D_OK);

		if (renderTargetAsTexture)
		{
			renderTargetAsTexture->GetSurfaceLevel(0, &renderTarget);
		}

		cr::ClearObject(hRenderSurface->m_surfaceAsTexture);
		if (hRenderSurface->m_createTexture)
		{
			CR_ASSERT_MSG(renderTargetAsTexture, "Texture representation of surface is not available");
			hRenderSurface->m_surfaceAsTexture.m_format = hRenderSurface->m_format;
			hRenderSurface->m_surfaceAsTexture.m_width = hRenderSurface->m_width;
			hRenderSurface->m_surfaceAsTexture.m_height = hRenderSurface->m_height;
			hRenderSurface->m_surfaceAsTexture.m_numMips = 1;
			hRenderSurface->m_surfaceAsTexture.m_pInitData = 0;
			hRenderSurface->m_surfaceAsTexture.m_initDataSize = 0;
			hRenderSurface->m_surfaceAsTexture.m_internal.m_pTexture = renderTargetAsTexture;
		}

		renderSurfaceInternal.m_pSurface = renderTarget;
	}

	void DeviceDirectX9::CreateTexture(TextureHandle hTexture)
	{
		TextureInternal& textureInternal = hTexture->m_internal;
		cr::ClearObject(textureInternal);

		D3DXCreateTextureFromFileInMemory(m_pD3DDevice, hTexture->m_pInitData, hTexture->m_initDataSize, &textureInternal.m_pTexture);
		CR_ASSERT(textureInternal.m_pTexture);

		D3DSURFACE_DESC desc;
		textureInternal.m_pTexture->GetLevelDesc(0,&desc);
		hTexture->m_width = desc.Width;
		hTexture->m_height = desc.Height;
		hTexture->m_numMips = textureInternal.m_pTexture->GetLevelCount();
		hTexture->m_format = TypeConversion::ConvertBufferFormat(desc.Format);
	}

	void DeviceDirectX9::CreateStreamDescription(StreamDescriptionHandle hStreamDescription)
	{
		StreamDescriptionInternal& streamDescriptionInternal = hStreamDescription->m_internal;
		const D3DVERTEXELEMENT9 unused = D3DDECL_END();

		cr::uint32_t usageIndex[MAXD3DDECLUSAGE];
		memset(usageIndex, 0, MAXD3DDECLUSAGE * sizeof(cr::uint32_t));
		static D3DVERTEXELEMENT9 vertexElements[Constants::MaxStreamElements+1];
		for (cr::size_t i = 0; i < Constants::MaxStreamElements+1; ++i)
		{
			if (i < hStreamDescription->m_numElements)
			{
 				vertexElements[i].Stream = 0;
 				vertexElements[i].Offset = (WORD)hStreamDescription->m_elements[i].m_offset;
				vertexElements[i].Type = (BYTE)TypeConversion::ConvertType(hStreamDescription->m_elements[i].m_hsType);
 				vertexElements[i].Method = D3DDECLMETHOD_DEFAULT;
				vertexElements[i].Usage = (BYTE)TypeConversion::ConvertSemantic(hStreamDescription->m_elements[i].m_hsSemantic);
 				vertexElements[i].UsageIndex = (BYTE)usageIndex[vertexElements[i].Usage]++;
			}
			else
			{
				vertexElements[i] = unused;
			}
		}

		m_pD3DDevice->CreateVertexDeclaration(vertexElements, &streamDescriptionInternal.m_pVertexDeclaration);
	}

	void DeviceDirectX9::CreateRenderBinding(RenderBindingHandle /*hRenderBinding*/)
	{

	}

	void DeviceDirectX9::CreateGpuProgram(GpuProgramHandle hGpuProgram)
	{
		GpuProgramInternal& gpuProgramInternal = hGpuProgram->m_internal;
		cr::ClearObject(gpuProgramInternal);

		ShaderHeader* pHeader = (ShaderHeader*)hGpuProgram->m_pInitData;
		CR_ASSERT(pHeader->m_fourcc == shared::prehash::hs_GPUP);

		cr::uint8_t* pCurPos = (cr::uint8_t*)(pHeader+1);

  		for (cr::size_t i = 0; i < pHeader->m_numPrograms; ++i)
  		{
			ProgramHeader* pProgram = (ProgramHeader*)pCurPos;
			
			switch (pProgram->m_programType)
			{
			case shared::prehash::hs_PixelShader:
				{
					HRESULT hRes = m_pD3DDevice->CreatePixelShader((const DWORD*)(pProgram+1), &hGpuProgram->m_internal.m_pPixelShader);
					CR_ASSERT(hRes == S_OK);
					break;
				}
			case shared::prehash::hs_VertexShader:
				{
					HRESULT hRes = m_pD3DDevice->CreateVertexShader((const DWORD*)(pProgram+1), &hGpuProgram->m_internal.m_pVertexShader);
					CR_ASSERT(hRes == S_OK);
					break;
				}
			}

			pCurPos += sizeof(ProgramHeader) + pProgram->m_codeSize;
 		}

		hGpuProgram->m_numConstantTables = pHeader->m_numConstantTables;

		cr::uint8_t* pConstantTableData = pCurPos;

		// First, count constants
		cr::size_t numConstants = 0;
		for (cr::size_t i = 0; i < pHeader->m_numConstantTables; ++i)
		{
			ConstantTableHeader* pConstantTableHeader = (ConstantTableHeader*)pCurPos;
			numConstants += pConstantTableHeader->numConstants;
			pCurPos += sizeof(ConstantTableHeader) + pConstantTableHeader->numConstants*sizeof(ConstantHeader);
		}

		if (numConstants > 0)
		{
			cr::size_t curConstant = 0;
			pCurPos = pConstantTableData;

			hGpuProgram->m_pConstants = new GpuProgramConstant[numConstants];

			for (cr::size_t i = 0; i < pHeader->m_numConstantTables; ++i)
			{
				ConstantTableHeader* pConstantTableHeader = (ConstantTableHeader*)pCurPos;
				hGpuProgram->m_constantTables[i].m_hsName = pConstantTableHeader->m_programType;
				hGpuProgram->m_constantTables[i].m_numConstants = pConstantTableHeader->numConstants;
				hGpuProgram->m_constantTables[i].m_pConstants = &hGpuProgram->m_pConstants[curConstant];
				curConstant += pConstantTableHeader->numConstants;

				pCurPos += sizeof(ConstantTableHeader);

				for (cr::size_t idxConstant = 0; idxConstant < pConstantTableHeader->numConstants; ++idxConstant)
				{
					ConstantHeader* pConstantHeader = (ConstantHeader*)pCurPos;
					hGpuProgram->m_constantTables[i].m_pConstants[idxConstant].m_hsName = pConstantHeader->m_constantName;
					hGpuProgram->m_constantTables[i].m_pConstants[idxConstant].m_hsType = pConstantHeader->m_constantType;
					hGpuProgram->m_constantTables[i].m_pConstants[idxConstant].m_offset = pConstantHeader->m_offset;
					hGpuProgram->m_constantTables[i].m_pConstants[idxConstant].m_size	= pConstantHeader->m_size;

					pCurPos += sizeof(ConstantHeader);
				}
			}
		}
	}

	void DeviceDirectX9::CreateStateBlock(StateBlockHandle hStateBlock)
	{
		cr::ClearObject(hStateBlock->m_internal);
	}

	void DeviceDirectX9::CreateConstantBuffer(ConstantBufferHandle hConstantBuffer)
	{
		cr::ClearObject(hConstantBuffer->m_internal);

		cr::size_t minIndex = 99999;
		cr::size_t maxIndex = 0;

		GpuProgramConstant* pConstant = hConstantBuffer->m_hGpuProgramTable->m_pConstants;
		for (cr::size_t i = 0; i < hConstantBuffer->m_hGpuProgramTable->m_numConstants; ++i, ++pConstant)
		{
			minIndex = cr::Maths::Min(pConstant->m_offset, minIndex);
			maxIndex = cr::Maths::Max(pConstant->m_offset+pConstant->m_size, maxIndex);
		}
			
		hConstantBuffer->m_internal.m_type = hConstantBuffer->m_hGpuProgramTable->m_hsName;
		hConstantBuffer->m_internal.m_registerCount = maxIndex-minIndex;
		hConstantBuffer->m_internal.m_startRegister = minIndex;

		switch (hConstantBuffer->m_internal.m_type.GetHash())
		{
		case shared::prehash::hs_PixelShaderTextures:
		case shared::prehash::hs_VertexShaderTextures:
			{
				hConstantBuffer->m_internal.m_textures = new ConstantBufferInternal::TextureRegister[hConstantBuffer->m_internal.m_registerCount];
				memset(hConstantBuffer->m_internal.m_textures, 0, sizeof(ConstantBufferInternal::TextureRegister)*hConstantBuffer->m_internal.m_registerCount);
				break;
			}
		case shared::prehash::hs_PixelShaderFloats:
		case shared::prehash::hs_VertexShaderFloats:
			{
				hConstantBuffer->m_internal.m_floats = new ConstantBufferInternal::FloatRegister[hConstantBuffer->m_internal.m_registerCount];
				memset(hConstantBuffer->m_internal.m_floats, 0, sizeof(ConstantBufferInternal::FloatRegister)*hConstantBuffer->m_internal.m_registerCount);
				break;
			}
		case shared::prehash::hs_PixelShaderInts:
		case shared::prehash::hs_VertexShaderInts:
			{
				hConstantBuffer->m_internal.m_ints = new ConstantBufferInternal::IntRegister[hConstantBuffer->m_internal.m_registerCount];
				memset(hConstantBuffer->m_internal.m_ints, 0, sizeof(ConstantBufferInternal::IntRegister)*hConstantBuffer->m_internal.m_registerCount);
				break;
			}
		case shared::prehash::hs_PixelShaderBools:
		case shared::prehash::hs_VertexShaderBools:
			{
				hConstantBuffer->m_internal.m_bools = new ConstantBufferInternal::BoolRegister[hConstantBuffer->m_internal.m_registerCount];
				memset(hConstantBuffer->m_internal.m_bools, 0, sizeof(ConstantBufferInternal::BoolRegister)*hConstantBuffer->m_internal.m_registerCount);
				break;
			}
		}
	}

	// Object destruction
	void DeviceDirectX9::ReleaseContext(ContextHandle hContext)
	{
		cr::ClearObject(hContext->m_internal);
	}

	void DeviceDirectX9::ReleaseBuffer(BufferHandle hBuffer)
	{ 
		switch (hBuffer->m_format)
		{
		case BufferFormat::Index16Stream:
		case BufferFormat::Index32Stream:
			{
				hBuffer->m_internal.m_pIndexBuffer->Release();
				break;
			}
		case BufferFormat::VertexStream:
			{
				hBuffer->m_internal.m_pVertexBuffer->Release();
				break;
			}
		default:
			{
				delete [] hBuffer->m_internal.m_pGenericBuffer;
				break;
			}
		}

		cr::ClearObject(hBuffer->m_internal);
	}

	void DeviceDirectX9::ReleaseRenderTarget(RenderTargetHandle hRenderTarget)
	{
		cr::ClearObject(hRenderTarget->m_internal);
	}

	void DeviceDirectX9::ReleaseRenderSurface(RenderSurfaceHandle hRenderSurface)
	{
		hRenderSurface->m_internal.m_pSurface->Release();
		cr::ClearObject(hRenderSurface->m_internal);
	}

	void DeviceDirectX9::ReleaseTexture(TextureHandle hTexture)
	{
		hTexture->m_internal.m_pTexture->Release();
		cr::ClearObject(hTexture->m_internal);
	}

	void DeviceDirectX9::ReleaseStreamDescription(StreamDescriptionHandle hStreamDescription)
	{
		hStreamDescription->m_internal.m_pVertexDeclaration->Release();
		cr::ClearObject(hStreamDescription->m_internal);
	}

	void DeviceDirectX9::ReleaseRenderBinding(RenderBindingHandle /*hRenderBinding*/)
	{

	}
	void DeviceDirectX9::ReleaseGpuProgram(GpuProgramHandle hGpuProgram)
	{
		delete[] hGpuProgram->m_pConstants;
		if (hGpuProgram->m_internal.m_pPixelShader)
			hGpuProgram->m_internal.m_pPixelShader->Release();
		if (hGpuProgram->m_internal.m_pVertexShader)
			hGpuProgram->m_internal.m_pVertexShader->Release();
		cr::ClearObject(hGpuProgram->m_internal);
	}

	void DeviceDirectX9::ReleaseStateBlock(StateBlockHandle hStateBlock)
	{
		cr::ClearObject(hStateBlock->m_internal);
	}

	void DeviceDirectX9::ReleaseConstantBuffer(ConstantBufferHandle hConstantBuffer)
	{
		switch (hConstantBuffer->m_internal.m_type.GetHash())
		{
		case shared::prehash::hs_PixelShaderTextures:
		case shared::prehash::hs_VertexShaderTextures:
			{
				delete [] hConstantBuffer->m_internal.m_textures;
				break;
			}
		case shared::prehash::hs_PixelShaderFloats:
		case shared::prehash::hs_VertexShaderFloats:
			{
				delete [] hConstantBuffer->m_internal.m_floats;
				break;
			}
		case shared::prehash::hs_PixelShaderInts:
		case shared::prehash::hs_VertexShaderInts:
			{
				delete [] hConstantBuffer->m_internal.m_ints;
				break;
			}
		case shared::prehash::hs_PixelShaderBools:
		case shared::prehash::hs_VertexShaderBools:
			{
				delete [] hConstantBuffer->m_internal.m_bools;
				break;
			}
		}

		cr::ClearObject(hConstantBuffer->m_internal);
	}
}



#endif // GK_DEVICE_DIRECTX9